use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};

/// 获取指定目录下的所有直接子目录
///
/// 该函数会遍历指定目录，返回所有直接子目录的路径列表。
/// 不会递归遍历子目录的子目录，只返回第一级子目录。
///
/// # 参数
/// - `dir`: 要扫描的目录路径，可以是相对路径或绝对路径
///
/// # 返回值
/// 返回包含所有子目录路径的 `Vec<PathBuf>` 向量
///
/// # 特性
/// - ✅ 忽略文件，只返回目录
/// - ✅ 自动跳过无法访问的目录项（权限不足、损坏的符号链接等）
/// - ✅ 返回完整的绝对路径
/// - ✅ 空目录返回空向量
/// - ✅ 目录不存在时返回空向量（不会panic）
///
/// # 示例
/// ```
/// use std::path::PathBuf;
/// use common_service::file_helper::get_sub_dirs;
///
/// let subdirs = get_sub_dirs(".");
/// for dir in subdirs {
///     println!("找到子目录: {:?}", dir);
/// }
/// ```
///
/// # 错误处理
/// 函数具有容错性，在以下情况下会返回空向量或部分结果：
/// - 目录不存在
/// - 没有读取权限
/// - 目录项损坏
/// - 符号链接指向不存在的目标
///
/// 如果需要更严格的错误处理，请使用 `std::fs::read_dir` 直接处理。
///
pub fn get_sub_dirs(dir: &str) -> Vec<PathBuf> {
    let mut sub_dirs = Vec::new();

    // 读取目录内容，如果失败（如目录不存在、无权限）则返回空向量
    if let Ok(entries) = fs::read_dir(dir) {
        // 遍历目录项，使用 flatten() 跳过错误的条目
        for entry in entries.flatten() {
            let path = entry.path();

            // 检查是否为目录（包括符号链接指向目录的情况）
            if path.is_dir() {
                sub_dirs.push(path);
            }
        }
    }

    sub_dirs
}

/// 获取子目录并排序（按名称升序）
///
/// # 示例
/// ```
/// use common_service::file_helper::get_sub_dirs_sorted;
///
/// let sorted_dirs = get_sub_dirs_sorted(".");
/// for dir in sorted_dirs {
///     println!("按顺序: {:?}", dir);
/// }
/// ```
pub fn get_sub_dirs_sorted(dir: &str) -> Vec<PathBuf> {
    let mut sub_dirs = get_sub_dirs(dir);

    // 按文件名字符串排序（忽略大小写）
    sub_dirs.sort_by(|a, b| {
        a.file_name()
            .unwrap_or_default()
            .to_string_lossy()
            .to_lowercase()
            .cmp(
                &b.file_name()
                    .unwrap_or_default()
                    .to_string_lossy()
                    .to_lowercase(),
            )
    });

    sub_dirs
}

/// 安全地获取子目录（Result版本）
///
/// 返回 Result 而不是静默忽略错误
///
/// # 返回值
/// - `Ok(Vec<PathBuf>)`: 成功获取子目录列表
/// - `Err(std::io::Error)`: 读取目录失败
///
/// # 示例
/// ```
/// use common_service::file_helper::get_sub_dirs_result;
///
/// match get_sub_dirs_result("/nonexistent") {
///     Ok(dirs) => println!("找到 {} 个子目录", dirs.len()),
///     Err(e) => eprintln!("错误: {}", e),
/// }
/// ```
pub fn get_sub_dirs_result(dir: &str) -> std::io::Result<Vec<PathBuf>> {
    let mut sub_dirs = Vec::new();

    for entry in fs::read_dir(dir)? {
        let entry = entry?;
        let path = entry.path();

        if path.is_dir() {
            sub_dirs.push(path);
        }
    }

    Ok(sub_dirs)
}

/// 获取指定目录下所有MP4文件，并按文件名开头的数字排序
///
/// 该函数会扫描指定目录，查找所有以`.mp4`为扩展名的文件，然后提取文件名开头连续的数字部分作为排序键，
/// 返回一个按数字升序排列的有序映射(BTreeMap)。
///
/// # 参数
/// * `dir` - 要扫描的目录路径
///
/// # 返回值
/// * `BTreeMap<i64, PathBuf>` - 有序映射，键为文件名开头的数字，值为对应的文件路径
///
/// # 特性
/// - 只处理文件名以数字开头的MP4文件
/// - 忽略非文件项和无法读取的目录项
/// - 文件名中的数字部分必须能够解析为i64类型
/// - 如果多个文件有相同的数字前缀，只保留第一个遇到的文件
/// - 不递归扫描子目录
///
/// # 示例
/// ```
/// use std::path::Path;
/// use std::collections::BTreeMap;
/// use common_service::file_helper::get_mp4_files_sorted;
///
/// let dir = Path::new("./videos");
/// let sorted_files = get_mp4_files_sorted(dir);
/// for (num, path) in sorted_files {
///     println!("{}: {:?}", num, path);
/// }
/// ```
///
/// # 错误处理
/// - 如果目录读取失败，返回空的BTreeMap
/// - 无法解析数字的文件会被忽略
/// - 文件系统权限问题会导致相应文件被跳过
pub fn get_mp4_files_sorted(dir: &Path) -> BTreeMap<i64, PathBuf> {
    let mut file_map = BTreeMap::new();

    // 读取目录内容，如果失败则返回空映射
    if let Ok(entries) = fs::read_dir(dir) {
        // 遍历目录中的所有条目，跳过无法读取的条目
        for entry in entries.flatten() {
            let path = entry.path();

            // 只处理普通文件，跳过目录和符号链接等
            if path.is_file() {
                // 检查文件扩展名是否为MP4（不区分大小写）
                if let Some(ext) = path.extension() {
                    if ext.eq_ignore_ascii_case("mp4") {
                        // 提取文件名（不含扩展名）
                        if let Some(stem) = path.file_stem() {
                            let stem_str = stem.to_string_lossy();

                            // 提取文件名开头的连续数字部分
                            let digits: String = stem_str
                                .chars()
                                .take_while(|c| c.is_ascii_digit())
                                .collect();

                            // 将数字字符串解析为i64整数
                            if let Ok(num) = digits.parse::<i64>() {
                                // 使用entry API确保每个数字只对应一个文件（保留第一个遇到的）
                                file_map.entry(num).or_insert(path);
                            }
                        }
                    }
                }
            }
        }
    }

    file_map
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use tempfile::TempDir;

    #[test]
    fn test_get_subdirectories() {
        // 创建临时目录结构
        let temp_dir = TempDir::new().unwrap();
        let base_path = temp_dir.path();

        // 创建几个子目录
        fs::create_dir_all(base_path.join("dir1")).unwrap();
        fs::create_dir_all(base_path.join("dir2")).unwrap();
        fs::create_dir_all(base_path.join("hidden.dir")).unwrap();

        // 创建一些文件（应该被忽略）
        fs::write(base_path.join("file.txt"), "content").unwrap();

        let sub_dirs = get_sub_dirs(base_path.to_str().unwrap());

        // 应该找到3个子目录
        assert_eq!(sub_dirs.len(), 3);

        // 检查是否包含预期的目录
        let dir_names: Vec<String> = sub_dirs
            .iter()
            .map(|p| p.file_name().unwrap().to_string_lossy().into_owned())
            .collect();

        assert!(dir_names.contains(&"dir1".to_string()));
        assert!(dir_names.contains(&"dir2".to_string()));
        assert!(dir_names.contains(&"hidden.dir".to_string()));
    }

    #[test]
    fn test_nonexistent_directory() {
        // 不存在的目录应该返回空向量而不是panic
        let sub_dirs = get_sub_dirs("/nonexistent/path/123456");
        assert!(sub_dirs.is_empty());
    }

    #[test]
    fn test_empty_directory() {
        let temp_dir = TempDir::new().unwrap();
        let sub_dirs = get_sub_dirs(temp_dir.path().to_str().unwrap());
        assert!(sub_dirs.is_empty());
    }
}
